﻿using Common.Logger;
using Common.MQ;
using MSATracert;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Common.Web
{
    /// <summary>
    /// 链路跟踪中间件
    /// </summary>
    public class LinkTracertMiddleware
    {
        private readonly RequestDelegate next;
        private readonly IConfiguration configuration;
        private const string RESPONSE_TIME_HEADER_NAME = "X-Response-Time-ms";
        private readonly IMessageQueueProvider kafkaProvider;
        /// <summary>
        /// 链路跟踪中间件
        /// </summary>
        /// <param name="next"></param>
        /// <param name="configuration"></param>
        /// <param name="mqProvider"></param>
        public LinkTracertMiddleware(RequestDelegate next, IConfiguration configuration, IEnumerable<IMessageQueueProvider> mqProvider)
        {
            this.next = next;
            this.configuration = configuration;
            this.kafkaProvider = mqProvider?.FirstOrDefault(x => x.QueueType == MessageQueueType.Kafka);
        }
        /// <summary>
        /// 链路跟踪中间件
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {
            try
            {
                Execute(context);
            }
            catch (Exception ex)
            {
                Log<LinkTracertMiddleware>.Error($"LinkTracertMiddleware:{ex.Message}", ex);
            }
            await next(context);
        }
        private void Execute(HttpContext context)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            context.Request.Headers.TryGetValue(LinkTracertPricipal.HEADER_NAME, out var connid);
            string traceId = connid;
            if (Thread.CurrentPrincipal != null)
            {
                Utils.WriteConsole($"当前线程Principal不为空，{Thread.CurrentPrincipal.GetType()}");
            }
            if (string.IsNullOrEmpty(traceId))
            {
                traceId = Guid.NewGuid().ToString("N").ToLower();
            }
            var principal = new LinkTracertPricipal(traceId, Thread.CurrentPrincipal);

            context.Request.Headers.TryGetValue("X-Forwarded-For", out StringValues sValues);
            principal.IPAddress = GetFirstProxyAddress(sValues.FirstOrDefault());

            if (context.Request.Headers.TryGetValue("x-token", out StringValues xToken))
            {
                principal.Tokens.Add(new ApiToken { Name = "x-token", Value = xToken });
            }
            if (context.Request.Headers.TryGetValue("Authorization", out StringValues auth))
            {
                principal.Tokens.Add(new ApiToken { Name = "Authorization", Value = auth });
            }

            context.Response.OnStarting(watch => OnResponseStarting(watch, context, principal), stopwatch);

            Thread.CurrentPrincipal = principal;
        }
        private Task OnResponseStarting(object watch, HttpContext context, LinkTracertPricipal principal)
        {
            var sw = watch as Stopwatch;
            sw.Stop();
            context.Response.Headers.Add(RESPONSE_TIME_HEADER_NAME, sw.ElapsedMilliseconds.ToString());
            context.Response.Headers.Add(LinkTracertPricipal.HEADER_NAME, principal.RequestId);
            context.Request.Headers.TryGetValue("User-Agent", out StringValues ua);
            string topic = this.configuration["Env"] == "production" ? "measure-production" : "measure-test";
            var feature = context.Features.Get<HttpStatusCodeWrapperFeature>();
            var measureModel = new HttpMeasureModel
            {
                AppId = configuration["AppId"], //Environment.GetEnvironmentVariable("AppId"),
                AppName = configuration["AppName"],// Environment.GetEnvironmentVariable("AppName"),
                Env = configuration["Env"], //Environment.GetEnvironmentVariable("Env"),
                Elapsed = sw.ElapsedMilliseconds,
                Host = context.Request.Host.ToString(),
                Method = context.Request.Method,
                Path = context.Request.Path,
                RequestId = principal.RequestId,
                QueryString = context.Request.QueryString.Value,
                StatusCode = feature != null ? (int)feature.OriStatusCode : context.Response.StatusCode,
                Scheme = context.Request.Scheme,
                Destination = topic,
                TimeStampMs = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
                UserAgent = ua.ToString()
            };
            kafkaProvider?.Producer?.ProduceAsync(measureModel);
            return Task.CompletedTask;
        }
        private static string GetFirstProxyAddress(string xff)
        {
            if (string.IsNullOrEmpty(xff))
            {
                return null;
            }
            string[] array = xff.Split(',')?.Select(x => x.Trim()).ToArray();
            string first = array.First();
            return first.Contains(':') ? first.Substring(0, first.IndexOf(':')) : first;
        }
    }
}
